package com.jlf.core.engine.builder;

import com.jlf.core.engine.domain.SchemaDomainDO;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.objectweb.asm.*;

import java.io.File;
import java.io.FileOutputStream;


/**
 * 模型领域的管理服务的类构建器
 *
 * @author wujr
 * 2023/7/9
 * @version 1.0
 * 【变更历史】
 * [wujr 2023/7/9 1.0 新增]
 */
@Slf4j
@Getter
public class AutoServerBuilder {
    private final boolean saveServerClass = false;
    private Class<?> serverClass;
    private Class<?> serverImplClass;
    private String asmServerInternal;
    private String asmServerImplInternal;
    private String serverType;
    private String serverImplType;

    private final AutoPersistenceBuilder persistenceBuilder;
    private final AutoDomainBuilder domainBuilder;
    private final SchemaDomainDO schemaDomain;

    public AutoServerBuilder(AutoPersistenceBuilder persistenceBuilder,
                             SchemaDomainDO schemaDomain){
        this.persistenceBuilder = persistenceBuilder;
        this.domainBuilder = persistenceBuilder.getDomainBuilder();
        this.schemaDomain = schemaDomain;
    }

    /**
     * 构建模型的server接口
     * @throws Exception 构建失败，抛出异常
     */
    public Class<?> generate() throws Exception{
        String serverClass = this.domainBuilder.getBasePackage() + ".server." + this.domainBuilder.getDomainSimpleCode() + "Server";
        try{
            this.serverClass = this.domainBuilder.getEngineClassLoader().loadClass(serverClass);
            this.asmServerInternal = Type.getInternalName(this.serverClass);
            this.serverType = Type.getDescriptor(this.serverClass);
            return this.serverClass;
        }catch (Exception e){
            log.trace("加载模型领域：{} 的server，该server不存在，需要动态创建", this.domainBuilder.getDomainSimpleCode());
        }
        this.asmServerInternal = AutoBuilderTool.class2Asm(serverClass);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        String signature = AutoBuilderTool.asmSimpleServerSignature("L" + this.domainBuilder.getAsmDomainInternal() + ";");
        cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT, asmServerInternal, signature, "java/lang/Object", new String[]{AutoBuilderTool._asmSimpleServerInternal});
        cw.visitEnd();
        byte[] code = cw.toByteArray();
        if (saveServerClass) {
            String tempPath = System.getProperty("java.io.tmpdir") + File.separator;
            FileOutputStream fos = new FileOutputStream(tempPath + this.domainBuilder.getDomainSimpleCode() + "Server.class");
            fos.write(code);
            fos.close();
        }
        this.serverClass = this.domainBuilder.getEngineClassLoader().defineClass(serverClass, code);
        this.serverType = Type.getDescriptor(this.serverClass);
        return this.serverClass;
    }

    /**
     * 构建模型的服务的实现类
     * @throws Exception 构建失败，抛出异常
     */
    public Class<?> generateImpl() throws Exception{
        String serverPackage = serverClass.getPackage().getName();
        String implClass = serverPackage + ".impl." + serverClass.getSimpleName() + "Impl";
        try{
            this.serverImplClass = this.domainBuilder.getEngineClassLoader().loadClass(implClass);
            this.asmServerImplInternal = Type.getInternalName(this.serverImplClass);
            this.serverImplType = Type.getDescriptor(this.serverImplClass);
            return this.serverImplClass;
        }catch (Exception e){
            log.trace("加载模型领域：{} 的serverImpl，该serverImpl不存在，需要动态创建", this.domainBuilder.getDomainSimpleCode());
        }
        this.asmServerImplInternal = AutoBuilderTool.class2Asm(implClass);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        String signature = AutoBuilderTool.asmSimpleServerImplSignature("L" + this.domainBuilder.getAsmDomainInternal() + ";", "L" + asmServerInternal + ";");
        String[] interfaces = {asmServerInternal};
        cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, asmServerImplInternal, signature, AutoBuilderTool._asmSimpleServerImplInternal, interfaces);
        // 添加构造函数
        String initDesc = "(L" + this.persistenceBuilder.getAsmPersistenceInternal() + ";)V";
        String superDesc = "(" + AutoBuilderTool._asmSimplePersistenceType + ")V";
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, AutoBuilderTool._asmInitName, initDesc, null,null);
        mv.visitVarInsn(Opcodes.ALOAD,0);
        mv.visitVarInsn(Opcodes.ALOAD,1);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, AutoBuilderTool._asmSimpleServerImplInternal, AutoBuilderTool._asmInitName, superDesc,false);
        mv.visitInsn(Opcodes.RETURN);
        // 更新操作数栈
        mv.visitMaxs(2,2);
        // 一定要有visitEnd
        mv.visitEnd();
        // 重载add、edit、delete、query的方法
        // add
        visitAdd(cw);
        // edit
        visitEdit(cw);
        // delete
        visitDelete(cw);
        // query
        visitQuery(cw);
        // getById
        visitView(cw);

        // 最后，类结束
        cw.visitEnd();
        byte[] code = cw.toByteArray();
        if (saveServerClass) {
            String tempPath = System.getProperty("java.io.tmpdir") + File.separator;
            FileOutputStream fos = new FileOutputStream(tempPath + this.domainBuilder.getDomainSimpleCode() + "ServerImpl.class");
            fos.write(code);
            fos.close();
        }
        this.serverImplClass = this.domainBuilder.getEngineClassLoader().defineClass(implClass, code);
        this.serverImplType = Type.getDescriptor(this.serverImplClass);
        return serverImplClass;
    }

    private void visitAdd(ClassWriter cw) throws Exception{
        // 重载add方法
        // T add(T record)throws Exception;
        String addDesc = "(L" + this.domainBuilder.getAsmDomainInternal() + ";)L" + this.domainBuilder.getAsmDomainInternal() + ";";
        String superAddDesc = "(" + AutoBuilderTool._asmSimpleType + ")" + AutoBuilderTool._asmSimpleType;
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "add", addDesc, null, new String[]{AutoBuilderTool._asmExceptionInternal});
        AnnotationVisitor av = mv.visitAnnotation(AutoBuilderTool._asmOverrideType, true);
        av.visitEnd();
        if (schemaDomain.isAddNeedTransaction()) {
            av = mv.visitAnnotation(AutoBuilderTool._asmTransactionType, true);
            AnnotationVisitor av1 = av.visitArray("rollbackFor");
            av1.visit(null, Type.getType(Exception.class));
            av1.visit(null, Type.getType(RuntimeException.class));
            av1.visitEnd();
            av.visitEnd();
        }
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD,0);
        mv.visitVarInsn(Opcodes.ALOAD,1);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, AutoBuilderTool._asmSimpleServerImplInternal, "add", superAddDesc,true);
        mv.visitTypeInsn(Opcodes.CHECKCAST, this.domainBuilder.getAsmDomainInternal());
        mv.visitInsn(Opcodes.ARETURN);
        // 更新操作数栈
        mv.visitMaxs(2,2);
        mv.visitEnd();

        // void add(AddRecordWrapper<domainClass> wrapper) throws Exception;
        addDesc = "(" + AutoBuilderTool._asmAddRecordWrapperType + ")V";
        String addSignature = "(" + AutoBuilderTool._asmAddRecordWrapperSignature + "<L" + this.domainBuilder.getAsmDomainInternal() + ";>;)V";
        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "add", addDesc, addSignature, new String[]{AutoBuilderTool._asmExceptionInternal});
        av = mv.visitAnnotation(AutoBuilderTool._asmOverrideType, true);
        av.visitEnd();
        if (schemaDomain.isAddNeedTransaction()) {
            av = mv.visitAnnotation(AutoBuilderTool._asmTransactionType, true);
            AnnotationVisitor av1 = av.visitArray("rollbackFor");
            av1.visit(null, Type.getType(Exception.class));
            av1.visit(null, Type.getType(RuntimeException.class));
            av1.visitEnd();
            av.visitEnd();
        }
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD,0);
        mv.visitVarInsn(Opcodes.ALOAD,1);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, AutoBuilderTool._asmSimpleServerImplInternal, "add", addDesc,true);
        mv.visitInsn(Opcodes.RETURN);
        // 更新操作数栈
        mv.visitMaxs(2,2);
        mv.visitEnd();
    }
    private void visitEdit(ClassWriter cw) throws Exception{
        // 重载edit方法
        // T edit(T original, T record)throws Exception;
        String editDesc = "(L" + this.domainBuilder.getAsmDomainInternal() + ";L" + this.domainBuilder.getAsmDomainInternal() + ";)L" + this.domainBuilder.getAsmDomainInternal() + ";";
        String superEditDesc = "(" + AutoBuilderTool._asmSimpleType + AutoBuilderTool._asmSimpleType + ")" + AutoBuilderTool._asmSimpleType;
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "edit", editDesc, null, new String[]{AutoBuilderTool._asmExceptionInternal});
        AnnotationVisitor av = mv.visitAnnotation(AutoBuilderTool._asmOverrideType, true);
        av.visitEnd();
        if (schemaDomain.isEditNeedTransaction()) {
            av = mv.visitAnnotation(AutoBuilderTool._asmTransactionType, true);
            AnnotationVisitor av1 = av.visitArray("rollbackFor");
            av1.visit(null, Type.getType(Exception.class));
            av1.visit(null, Type.getType(RuntimeException.class));
            av1.visitEnd();
            av.visitEnd();
        }
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD,0);
        mv.visitVarInsn(Opcodes.ALOAD,1);
        mv.visitVarInsn(Opcodes.ALOAD,2);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, AutoBuilderTool._asmSimpleServerImplInternal, "edit", superEditDesc,true);
        mv.visitTypeInsn(Opcodes.CHECKCAST, this.domainBuilder.getAsmDomainInternal());
        mv.visitInsn(Opcodes.ARETURN);
        // 更新操作数栈
        mv.visitMaxs(3,3);
        mv.visitEnd();

        // void edit(EditRecordWrapper<domainClass> wrapper) throws Exception;
        editDesc = "(" + AutoBuilderTool._asmEditRecordWrapperType + ")V";
        String editSignature = "(" + AutoBuilderTool._asmEditRecordWrapperSignature + "<L" + this.domainBuilder.getAsmDomainInternal() + ";>;)V";
        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "edit", editDesc, editSignature, new String[]{AutoBuilderTool._asmExceptionInternal});
        av = mv.visitAnnotation(AutoBuilderTool._asmOverrideType, true);
        av.visitEnd();
        if (schemaDomain.isEditNeedTransaction()) {
            av = mv.visitAnnotation(AutoBuilderTool._asmTransactionType, true);
            AnnotationVisitor av1 = av.visitArray("rollbackFor");
            av1.visit(null, Type.getType(Exception.class));
            av1.visit(null, Type.getType(RuntimeException.class));
            av1.visitEnd();
            av.visitEnd();
        }
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD,0);
        mv.visitVarInsn(Opcodes.ALOAD,1);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, AutoBuilderTool._asmSimpleServerImplInternal, "edit", editDesc,true);
        mv.visitInsn(Opcodes.RETURN);
        // 更新操作数栈
        mv.visitMaxs(2,2);
        mv.visitEnd();
    }
    private void visitQuery(ClassWriter cw) throws Exception{
        // 重载query方法
        // ListWrapper<T> query(Q input, Integer pageNo, Integer pageSize)
        String queryDesc = "(" + AutoBuilderTool._asmQType + AutoBuilderTool._asmIntegerType + AutoBuilderTool._asmIntegerType + ")" + AutoBuilderTool._asmListWrapperType;
        String querySignature = "(" + AutoBuilderTool._asmQSignature + "<L" + this.domainBuilder.getAsmDomainInternal() + ";>;" + AutoBuilderTool._asmIntegerType + AutoBuilderTool._asmIntegerType + ")" + AutoBuilderTool._asmListWrapperSignature + "<L" + this.domainBuilder.getAsmDomainInternal() + ";>;";
        String superDesc = "(" + AutoBuilderTool._asmQType + AutoBuilderTool._asmIntegerType + AutoBuilderTool._asmIntegerType + ")" + AutoBuilderTool._asmListWrapperType;
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "query", queryDesc, querySignature, null);
        AnnotationVisitor av = mv.visitAnnotation(AutoBuilderTool._asmOverrideType, true);
        av.visitEnd();
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD,0);
        mv.visitVarInsn(Opcodes.ALOAD,1);
        mv.visitVarInsn(Opcodes.ALOAD,2);
        mv.visitVarInsn(Opcodes.ALOAD,3);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, AutoBuilderTool._asmSimpleServerImplInternal, "query", superDesc,false);
        mv.visitInsn(Opcodes.ARETURN);
        // 更新操作数栈
        mv.visitMaxs(4,4);
        mv.visitEnd();
    }
    private void visitView(ClassWriter cw) throws Exception{
        // 重载query方法
        // T getById(Serializable id)
        String desc = "(" + AutoBuilderTool._asmSerializableType + ")" + this.domainBuilder.getDomainType();
        String superDesc = "(" + AutoBuilderTool._asmSerializableType + ")" + AutoBuilderTool._asmSimpleType;
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "getById", desc, null, null);
        AnnotationVisitor av = mv.visitAnnotation(AutoBuilderTool._asmOverrideType, true);
        av.visitEnd();
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD,0);
        mv.visitVarInsn(Opcodes.ALOAD,1);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, AutoBuilderTool._asmSimpleServerImplInternal, "getById", superDesc,false);
        mv.visitTypeInsn(Opcodes.CHECKCAST, this.domainBuilder.getAsmDomainInternal());
        mv.visitVarInsn(Opcodes.ASTORE, 2); // record
        // 更新操作数栈
        mv.visitMaxs(3,3);
        mv.visitVarInsn(Opcodes.ALOAD, 2);
        mv.visitInsn(Opcodes.ARETURN);
        mv.visitEnd();
    }
    private void visitDelete(ClassWriter cw) throws Exception{
        // 重载delete方法
        // T delete(T record) throws Exception
        String delDesc = "(L" + this.domainBuilder.getAsmDomainInternal() + ";)L" + this.domainBuilder.getAsmDomainInternal() + ";";
        String superDelDesc = "(" + AutoBuilderTool._asmSimpleType + ")" + AutoBuilderTool._asmSimpleType;
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "delete", delDesc, null, new String[]{AutoBuilderTool._asmExceptionInternal});
        AnnotationVisitor av = mv.visitAnnotation(AutoBuilderTool._asmOverrideType, true);
        av.visitEnd();
        if (schemaDomain.isAddNeedTransaction()) {
            av = mv.visitAnnotation(AutoBuilderTool._asmTransactionType, true);
            AnnotationVisitor av1 = av.visitArray("rollbackFor");
            av1.visit(null, Type.getType(Exception.class));
            av1.visit(null, Type.getType(RuntimeException.class));
            av1.visitEnd();
            av.visitEnd();
        }
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD,0);
        mv.visitVarInsn(Opcodes.ALOAD,1);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, AutoBuilderTool._asmSimpleServerImplInternal, "delete", superDelDesc,true);
        mv.visitTypeInsn(Opcodes.CHECKCAST, this.domainBuilder.getAsmDomainInternal());
        mv.visitInsn(Opcodes.ARETURN);
        // 更新操作数栈
        mv.visitMaxs(2,2);
        mv.visitEnd();

        // void add(AddRecordWrapper<domainClass> wrapper) throws Exception;
        delDesc = "(" + AutoBuilderTool._asmDeleteRecordWrapperType + ")V";
        String delSignature = "(" + AutoBuilderTool._asmDeleteRecordWrapperSignature + "<L" + this.domainBuilder.getAsmDomainInternal() + ";>;)V";
        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "delete", delDesc, delSignature, new String[]{AutoBuilderTool._asmExceptionInternal});
        av = mv.visitAnnotation(AutoBuilderTool._asmOverrideType, true);
        av.visitEnd();
        if (schemaDomain.isAddNeedTransaction()) {
            av = mv.visitAnnotation(AutoBuilderTool._asmTransactionType, true);
            AnnotationVisitor av1 = av.visitArray("rollbackFor");
            av1.visit(null, Type.getType(Exception.class));
            av1.visit(null, Type.getType(RuntimeException.class));
            av1.visitEnd();
            av.visitEnd();
        }
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD,0);
        mv.visitVarInsn(Opcodes.ALOAD,1);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, AutoBuilderTool._asmSimpleServerImplInternal, "delete", delDesc,true);
        mv.visitInsn(Opcodes.RETURN);
        // 更新操作数栈
        mv.visitMaxs(2,2);
        mv.visitEnd();
    }
}
